﻿using System;
using System.Linq;
using System.Reflection;
using Supermodel.DomainModel;
using Supermodel.Exceptions;

namespace Supermodel.Reflection
{
    public static class ReflectionHelpers
    {
        #region Methods
        public static object CreateType(Type type, params object[] args)
        {
            return Activator.CreateInstance(type, args);
        }
        public static object CreateGenericType(Type genericType, Type innerType, params object[] args)
        {
            Type specificType = genericType.MakeGenericType(new[] { innerType });
            return Activator.CreateInstance(specificType, args);
        }
        public static object ExecuteStaticGenericMethod(Type typeofClassWithGenericStaticMethod, string methodName, Type[] genericArguments, params object[] args)
        {
            var methodInfo = typeofClassWithGenericStaticMethod.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
            var genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);
            return genericMethodInfo.Invoke(null, args);
        }
        public static bool IsClassADrivedFromClassB(Type a, Type b)
        {
            if (a == b) return true;
            Type aBaseType = a.BaseType;
            if (aBaseType == null) return false;
            if (aBaseType.Name == b.Name) return true;
            return IsClassADrivedFromClassB(aBaseType, b);
        }
        public static bool IsEntityType(Type type)
        {
            return IsClassADrivedFromClassB(type, typeof(Entity<>));
        }
        public static bool IsCollectionOfEntities(Type type)
        {
            var icollectionInterfaceType = type.GetInterface("Icollection`1");
            if (icollectionInterfaceType == null) return false;
            var entityType = icollectionInterfaceType.GetGenericArguments()[0];
            return IsEntityType(entityType);
        }
        #endregion

        #region Extnesions for Fluent interface
        public static object ExecuteMethod(this object me, string methodName, params object[] args)
        {
            try
            {
                //var methodInfo = me.GetType().GetMethod(methodName, BindingFlags.Public);
                return me.GetType().GetMethods().Where(x => x.Name == methodName).First().Invoke(me, args);
            }
            catch (Exception)
            {
                throw new SupermodelReflectionMethodCantBeInvoked(me.GetType(), methodName);
            }
        }
        public static object PropertyGet(this object me, string propertyName, object[] index = null)
        {
            return me.GetProperty(propertyName).GetValue(me, index);
        }
        public static void PropertySet(this object me, string propertyName, object newValue, bool ignoreNoSetMethod = false, object[] index = null)
        {
            try
            {
                me.GetProperty(propertyName).SetValue(me, newValue, index);
            }
            catch (ArgumentException)
            {
                if (!ignoreNoSetMethod) throw;
            }
        }

        #endregion

        #region Private Helpers
        private static PropertyInfo GetProperty(this object me, string propertyName)
        {
            //var tempPropertyHolder = me.GetType().GetProperties().Where(x => x.Name == propertyName);
            try
            {
                return me.GetType().GetProperty(propertyName);
            }
            catch(Exception)
            {
                throw new SupermodelReflectionPropertyCantBeInvoked(me.GetType(), propertyName);
            }
        } 
        #endregion

    }
}
